package structplugin

import (
	"encoding"
	"fmt"
	"reflect"
)

func Marshal(obj any) (map[string]interface{}, error) {
	srv := reflect.ValueOf(obj)
	st := srv.Type()
	if srv.Kind() == reflect.Ptr {
		srv = srv.Elem()
	}

	var err error
	if srv.Kind() == reflect.Struct {
		l := srv.NumField()
		ret := make(map[string]interface{}, l)
		for i := 0; i < l; i++ {
			fd := st.Field(i)
			fv := srv.Field(i)
			name := fd.Name
			ret[name], err = convert(fv)
			if err != nil {
				return nil, err
			}
		}
		return ret, nil
	} else {
		return nil, fmt.Errorf("unknown type: %s", st.Name())
	}
}

func convert(sv reflect.Value) (any, error) {
	if !sv.IsValid() {
		return nil, nil
	}
	switch sv.Kind() {
	case reflect.Ptr:
		sv = sv.Elem()
		return convert(sv)
	case reflect.Int64, reflect.Int32, reflect.Int, reflect.Int16, reflect.Int8:
		return sv.Int(), nil
	case reflect.Uint64, reflect.Uint32, reflect.Uint, reflect.Uint16, reflect.Uint8:
		return sv.Uint(), nil
	case reflect.Float32, reflect.Float64:
		return sv.Float(), nil
	case reflect.Bool:
		return sv.Bool(), nil
	case reflect.String:
		return sv.String(), nil
	case reflect.Complex64, reflect.Complex128:
		return sv.Complex(), nil
	default:
		if sv.Type().Implements(textMarshaller) {
			js, err := sv.Interface().(encoding.TextMarshaler).MarshalText()
			if err != nil {
				return nil, err
			}
			return string(js), nil
		}
		return nil, fmt.Errorf("unknown type: %s", sv.Type().Name())
	}
}
